﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace WinAction.Hook
{
	public static class MouseHook
	{
		[StructLayout(LayoutKind.Sequential)]
		private class Point
		{
			public int x;
			public int y;
		}

		[StructLayout(LayoutKind.Sequential)]
		private class MouseHookStruct
		{
			public Point pt;
			public int hWnd;
			public int wHitTestCode;
			public IntPtr dwExtraInfo;
		}

		[StructLayout(LayoutKind.Sequential)]
		private class MouseWheelHookStruct
		{
			public Point pt;
			public int mouseData;
			public int flags;
			public int time;
			public IntPtr dwExtraInfo;
		}

		// 装置钩子的函数
		[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
		private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

		// 卸下钩子的函数
		[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
		private static extern bool UnhookWindowsHookEx(int idHook);

		// 下一个钩挂的函数
		[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
		private static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);

		// 当前模块句柄
		[DllImport("kernel32.dll")]
		private static extern IntPtr GetModuleHandle(string name);

		// 钩子回调函数
		private delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

		// 声明鼠标钩子事件类型
		private static HookProc mouseHookProcedure;

		// 鼠标钩子句柄
		private static volatile int hMouseHook = 0;

		// 锁
		private static readonly object locker = new object();

		// 事件表
		private static ConcurrentDictionary<string, Action<MouseEvent>> eventDic = new ConcurrentDictionary<string, Action<MouseEvent>>();

		private static void StartHook()
		{
			lock (locker)
			{
				if (hMouseHook == 0)
				{
					// 生成一个HookProc的实例.
					mouseHookProcedure = new HookProc(MouseHookProc);

					hMouseHook = SetWindowsHookEx((int)HookType.WH_MOUSE_LL, mouseHookProcedure, GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0);

					//如果装置失败停止钩子
					if (hMouseHook == 0)
					{
						StopHook();
						throw new Exception($"监听鼠标Hook失败, code:{Marshal.GetLastWin32Error()}.");
					}
				}
			}
		}

		private static void StopHook()
		{
			lock (locker)
			{
				bool retMouse = true;

				if (hMouseHook != 0)
				{
					retMouse = UnhookWindowsHookEx(hMouseHook);
					hMouseHook = 0;
				}

				// 如果卸下钩子失败
				if (!retMouse)
				{
					throw new Exception("UnhookWindowsHookEx failed.");
				}
			}
		}

		/// <summary>
		/// 注册一个鼠标监听事件
		/// </summary>
		/// <param name="keyEvent">鼠标事件</param>
		/// <returns>注册事件的ID</returns>
		public static string RegisterMouseHookEvent(Action<MouseEvent> mouseEvent)
		{
			if (mouseEvent == null)
			{
				throw new NullReferenceException("待注册的鼠标事件不可为null");
			}
			string id = Guid.NewGuid().ToString("N");
			if (eventDic.TryAdd(id, mouseEvent))
			{
				StartHook();
				return id;
			}

			return null;
		}

		/// <summary>
		/// 取消注册鼠标事件
		/// </summary>
		/// <param name="eventId">事件ID</param>
		public static bool UnRegisterMouseHookEvent(string eventId)
		{
			Action<MouseEvent> mouseEvent;
			bool res = eventDic.TryRemove(eventId, out mouseEvent);
			if (eventDic.Count <= 0)
			{
				StartHook();
			}

			return res;
		}

		/// <summary>
		/// 鼠标钩子回调函数
		/// </summary>
		private static int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
		{
			if (nCode >= 0)
			{
				MouseEvent mouseEvent;
				if (wParam == (IntPtr)MouseEventType.Wheel)
				{
					MouseWheelHookStruct mouseHookStruct = (MouseWheelHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseWheelHookStruct));
					mouseEvent = new MouseEvent()
					{
						X = mouseHookStruct.pt.x,
						Y = mouseHookStruct.pt.y,
						Event = (MouseEventType)(int)wParam,
						Hwnd = IntPtr.Zero,
						Delta = (short)((mouseHookStruct.mouseData >> 16) & 0xffff)
					};
				}
				else
				{
					MouseHookStruct mouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
					mouseEvent = new MouseEvent()
					{
						X = mouseHookStruct.pt.x,
						Y = mouseHookStruct.pt.y,
						Event = (MouseEventType)(int)wParam,
						Hwnd = new IntPtr(mouseHookStruct.hWnd),
						Delta = 0
					};
				}
				foreach (var eventPair in eventDic)
				{
					eventPair.Value?.Invoke(mouseEvent);
				}
			}
			// 启动下一次钩子
			return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
		}

	}

	/// <summary>
	/// 鼠标事件枚举
	/// </summary>
	public enum MouseEventType
	{
		Move = 0x200,
		LeftDown = 0x201,
		LeftUp = 0x202,
		LeftDb = 0x203,
		RightDown = 0x204,
		RightUp = 0x205,
		RightDb = 0x206,
		MiddleDown = 0x207,
		MiddleUp = 0x208,
		MiddleDb = 0x209,
		Wheel = 0x20A
	}

	public class MouseEvent
	{
		public int X { get; set; }

		public int Y { get; set; }

		public MouseEventType Event { get; set; }

		public IntPtr Hwnd { get; set; }

		public int Delta { get; set; }
	}
}
